﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics.Contracts;
using System.Linq;

using ProjectBase.Core;

namespace $safeprojectname$.Db
{
    /// <summary>
    ///     Base for ADO.NET 'ReadDao'.
    ///     Exposes the DbCommand for CRUD operations, which can be encapsulated in transaction.
    ///     (use the the [Transaction] attribute on controller, or e.g. for testing explicitly call Begin and Rollback)
    ///
    ///     If another IDbCommand than SqlClient is used, change
    ///     ProjectSettingsProvider 'IDbCommand NewDbCommand()' implemenation.
    ///     This one-place change will grant that every DbDao will be using your own IDbCommand
    /// </summary>
    /// <typeparam name = "T">Templated entity which will be read via this Dao</typeparam>
    /// <typeparam name = "TID">the template for the ID type. currently supported are decimal, int, short, long, Guid, string</typeparam>
    /// <typeparam name = "TFilter"></typeparam>
    ///<typeparam name="TCommand"></typeparam>
    public abstract class ReadDbDao<T, TID, TFilter, TCommand> : DbDao<TCommand>, IReadDao<T, TID, TFilter>
        where T : class, IPersistentObjectWithTypedId<TID>, ISetID<TID>, new()
        where TFilter : class, IFilter<TID>
        where TCommand : class, IDbCommand
    {
        public abstract IEnumerable<T> Find(TFilter filter);
        public abstract T GetById(TID id);

        #region Get implemented

        public virtual IEnumerable<T> Find(TID exceptID, TFilter filter)
        {
            return Find(filter).Where(entity => !entity.ID.Equals(exceptID)).ToList();
        }

        /// <summary>
        ///     Provides Ordering and Paging
        /// </summary>
        /// <param name = "filter"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        protected virtual IList<T> GetListByCriteria(IFilter filter, IList<T> list = null)
        {
            if (list.IsNull())
            {
                list = ExecuteReaderToList();
            }
            // use Reader to fill the list
            if (list.IsNull())
            {
                return null; // Reader was not executed succesfully
            }
            list = AddOrder(list, filter);

            // Adjust the Paging, when not existing page is demanded
            filter.TotalRows = list.Count();
            
            if (filter.SkipRows> filter.TotalRows)
            {
                filter.CurrentPage = (filter.TotalRows / filter.TakeRows) - 1;
            }

            // Execute the Paging
            var result = list.Skip(filter.SkipRows).Take(filter.TakeRows).ToList();
            return result;
        }

        /// <summary>
        ///     Provides basic Ordering based on the property name.
        ///     If any smarter needed - do override.
        /// </summary>
        /// <param name = "list"></param>
        /// <param name = "filter"></param>
        protected virtual IList<T> AddOrder(IList<T> list, IFilter filter)
        {
            Contract.Requires(list.Is(), " Cannot AddOrder to null instance. Provide the IEnumerable<T> list instead");
            Contract.Requires(filter.Is(), " Cannot AddOrder to null instance. Provide the IEnumerable<T> list instead");

            foreach (var orderBy in filter.OrderBy)
            {
                if (typeof(T).GetProperties().Any(p => p.Name.Equals(orderBy.Key, StringComparison.Ordinal)))
                {
                    if (orderBy.Value)
                    {
                        list =
                            list.OrderBy(entity => entity.GetType().GetProperty(orderBy.Key).GetValue(entity, null)).ToList();
                    }
                    else
                    {
                        list =
                            list.OrderByDescending(
                                entity =>
                                entity.GetType().GetProperty(orderBy.Key).GetValue(entity, null)).ToList();
                    }
                }
            }
            return list;
        }
        #endregion Get

        #region DataReader <-> Entity converters
        /// <summary>
        ///     Executes the 'DbCommand' Reader (with correct disposing of this resource).
        ///     CreateEntity() is called to map the result.
        /// </summary>
        /// <returns></returns>
        protected virtual IList<T> ExecuteReaderToList(IDbCommand command = null)
        {
            command = command ?? DbCommand;

            var list = new List<T>();

            try
            {
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        list.Add(CreateEntity(reader));
                    }
                }
                return list;
            }
            catch (Exception ex)
            {
                PublishingManager.Publish(this, "Cannot execute the reader: {0}".FormatWith(ex.Message), ex);
            }
            return null;
        }

        /// <summary>
        ///     Converts the row provided int DataReader into the 'T' entity properties.
        ///     Default implementation maps the same Column names to the same Property names
        /// </summary>
        /// <param name = "reader"></param>
        /// <returns></returns>
        protected virtual T CreateEntity(IDataReader reader)
        {
            var entity = new T();
            var desciptors = entity.GetType().GetPropertyDescriptors();

            for (var i = 0; i < reader.FieldCount; i++)
            {
                var name = reader.GetName(i);
                PropertyDescriptor property;
                if (desciptors.TryGetValue(name, out property)
                    && reader[name] != DBNull.Value)
                {
                    property.SetValue(entity, reader[name]);
                }
            }
            return entity;
        }
        #endregion DataRow <-> Entity converters
    }
}